﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using NLite.Collections;
using NLite.Mini.Activation;
using NLite.Mini.Context;
using NLite.Mini.Lifestyle;
using NLite.Reflection;
using NLite.Mini.Proxy;
using NLite.Mini.Listener;
using NLite.Internal;
using NLite.Mini.Internal;

namespace NLite
{
   /// <summary>
   /// 
   /// </summary>
    [Serializable]
    public sealed class Kernel:BooleanDisposable, IKernel,ILazyServiceLocator
    {
        readonly IMap<string, ComponentPair> IdStores;
        readonly IMap<Type, List<ComponentPair>> TypeStores;
        readonly IComponentListener Listner;
        IActivatorFactory ActivatorRegistry;
        ILifestyleManagerFactory LifestyleManagerRegistry;

        /// <summary>
        /// 
        /// </summary>
        public Kernel():this(
            new ComponentListenManager()
            , new LifestyleManagerFactory()
            , new ActivatorFactory()
            , new ClassLoader())
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="listnerManager"></param>
        /// <param name="lifestyleManagerRegistry"></param>
        /// <param name="activatorFactory"></param>
        /// <param name="classLoader"></param>
        public Kernel(
            IComponentListenerManager listnerManager,
            ILifestyleManagerFactory lifestyleManagerRegistry,
            IActivatorFactory activatorFactory,
            IClassLoader classLoader)
        {
            ListenerManager = listnerManager;
            LifestyleManagerRegistry = lifestyleManagerRegistry;
            ActivatorRegistry = activatorFactory;

            IdStores = new ConcurrentMap<string, ComponentPair>(StringComparer.OrdinalIgnoreCase);
            TypeStores = new ConcurrentMap<Type, List<ComponentPair>>();

            Listner = ListenerManager as IComponentListener;

            ServiceLocator.Current = this;
            ServiceRegistry.Current = this;

            RegisterInstance("ServiceLocator", typeof(IServiceLocator), this);
            RegisterInstance("ServiceRegistry", typeof(IServiceRegistry), this);
            

            if (classLoader != null)
                RegisterInstance(AppDomain.CurrentDomain.Id.ToString() + ":" + classLoader.GetType(), typeof(IClassLoader), classLoader);

            if (ListenerManager != null)
                ListenerManager.Init(this);

            RegisterListnersAndInjectionManagers();
        }


        private void RegisterListnersAndInjectionManagers()
        {
            ListenerManager.Register(new AopListener());
            ListenerManager.Register(new DisposalListener());
            ListenerManager.Register(new InitializationListener());
            ListenerManager.Register(new SupportInitializeListener());
            ListenerManager.Register(new StartableListener());
            ListenerManager.Register(new SubscribeListener());
            //ListenerManager.Register(new LifecycleAdapterListener());
            ListenerManager.Register(new InjectionListener());
            ListenerManager.Register(new InjectionManyListener());
            ListenerManager.Register(new ComponentMemberRegisterListner());
            ListenerManager.Register(new AppSettingInjectionListener());
        }


        #region IKernel Members
      
        /// <summary>
        /// 
        /// </summary>
        public IComponentListenerManager ListenerManager { get; private set; }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool HasRegister(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");

            return IdStores.ContainsKey(id);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        /// <returns></returns>
        public bool HasRegister(Type contract)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");

            if (TypeStores.ContainsKey(contract))
                return true;
            if (!contract.IsGenericType)
                return false;

            var openGenericType = contract.GetGenericTypeDefinition();
            return TypeStores.ContainsKey(openGenericType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public IServiceRegistry Register(IComponentInfo info)
        {
            if (HasRegister(info.Id))
                throw new RepeatRegistrationException(
                       String.Format("There is a component already registered for the given key {0}", info.Id));

            var activator = CreateActivator(info);

            var lifestyleMgr = CreateLifestyleManager(info, activator);

            var pair = new ComponentPair(info, lifestyleMgr);
            IdStores[info.Id] = pair;

            if (info.Contracts.Length > 0)
            {
                foreach (var contract in info.Contracts)
                {
                    if (!TypeStores.ContainsKey(contract))
                        TypeStores[contract] = new List<ComponentPair>();
                    TypeStores[contract].Add(pair);
                }
            }

            Listner.OnMetadataRegistered(info);

            pair.LifestyleManager = CreateProxyLifestyleManager(info, pair.LifestyleManager);

            return this;
        }

        private ILifestyleManager CreateLifestyleManager(IComponentInfo info, IActivator activator)
        {
            var lifestyleMgr = LifestyleManagerRegistry.Create(info.Lifestyle);
            if (info.ExtendedProperties.Contains("proxy"))
                lifestyleMgr = new ProxyLifestyleManager(lifestyleMgr);

            lifestyleMgr.Init(activator, this, info, Listner.OnPreDestroy);
            return lifestyleMgr;
        }

        private ILifestyleManager CreateProxyLifestyleManager(IComponentInfo info, ILifestyleManager lifestyleMgr)
        {
            if (info.ExtendedProperties.Contains("proxy"))
                lifestyleMgr = new ProxyLifestyleManager(lifestyleMgr);

            return lifestyleMgr;
        }

        private IActivator CreateActivator(IComponentInfo info)
        {
            var activator = ActivatorRegistry.Create(info.Activator);

            var delegateActivator = activator as DelegateActivator;
            if (delegateActivator != null)
                delegateActivator.Creator = (ctx) => info.Factory();
            return activator;
        }

        IComponentInfo CreateComponentInfo(string id, Type contract, Type implementation, string activator, LifestyleFlags lifestyleType)
        {
            var info = new ComponentInfo(id, contract, implementation, activator, lifestyleType);
            return info;
        }

        PreCreationContext NewCreateContext(IDictionary<string, object> args, IComponentInfo info, params Type[] genericParameters)
        {
            return new PreCreationContext(this, info, args, genericParameters);
        }

        PreCreationContext NewCreateContext(IComponentInfo info, object[] args, params Type[] genericParameters)
        {
            return new PreCreationContext(this, info, args, genericParameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="contract"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public IServiceRegistry RegisterInstance(string id, Type contract, object instance)
        {
            Trace.Assert(contract != null, "contract == null");
            Trace.Assert(instance != null, "instance == null");
            Trace.Assert(contract.IsAssignableFrom(instance.GetType()), "!contract.IsAssignableFrom(instance.GetType())");

            if (string.IsNullOrEmpty(id))
                id = instance.GetType().FullName;

            if (HasRegister(id))
                throw new RepeatRegistrationException(
                       String.Format("There is a component already registered for the given key {0}", id));

            var info = CreateComponentInfo(id, contract, instance.GetType(), ActivatorType.Instance, LifestyleFlags.Singleton);
            info.ExtendedProperties["instance"]= instance;

            var activator = ActivatorRegistry.Create(info.Activator);

            var lifestyleMgr = CreateLifestyleManager(info, activator);
            lifestyleMgr.Init(activator, this, info, Listner.OnPreDestroy);

            var pair = new ComponentPair(info, lifestyleMgr);

            IdStores[id] = pair;

            foreach (var c in info.Contracts)
            {
                if (!TypeStores.ContainsKey(c))
                    TypeStores[c] = new List<ComponentPair>();
                TypeStores[c].Add(pair);
            }

            Listner.OnMetadataRegistered(info);

            pair.LifestyleManager = CreateProxyLifestyleManager(info, pair.LifestyleManager);

            Listner.OnPostCreation(new PostCreationContext(this,info,instance));

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public IServiceRegistry RegisterInstance(string id, object instance)
        {
            Trace.Assert(instance != null, "instance == null");
            return RegisterInstance(id, instance.GetType(), instance);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        public IServiceRegistry Compose(object instance)
        {
            Trace.Assert(instance != null, "instance == null");
            var info = new ComponentInfo(instance.GetType());

            var ctx = new PostCreationContext(this, info, instance);
            Listner.OnPostCreation(ctx);
            Listner.OnInitialization(ctx);
            Listner.OnPostInitialization(ctx);
            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        public void UnRegister(Type contract)
        {
            if (HasRegister(contract))
            {
                var pairs = TypeStores[contract];
                var ids = (from item in IdStores.Keys
                             from p in pairs
                             where IdStores[item].Component == p.Component
                             select item).ToArray();

                if (ids.Length > 0)
                    foreach (var id in ids)
                        IdStores.Remove(id);

                foreach (var pair in pairs)
                {
                    pair.Component.ExtendedProperties.Clear();
                    TypeStores.Remove(contract);
                    pair.LifestyleManager.Dispose();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void UnRegister(string id)
        {
            if (HasRegister(id))
            {
                var pair = IdStores[id];

                pair.Component.ExtendedProperties.Clear();
                IdStores.Remove(id);

                var contracts = (from item in TypeStores.Keys
                            from c in pair.Component.Contracts
                            from p in TypeStores[item]
                            where item == c && pair.Component == p.Component
                            select new { Contract = c,Pair = p}).ToArray();

                if (contracts.Length > 0)
                {
                    foreach (var contract in contracts)
                    {
                        var pairs = TypeStores[contract.Contract];
                        pairs.Remove(contract.Pair);
                        if (pairs.Count == 0)
                            TypeStores.Remove(contract.Contract);
                    }
                }

                pair.LifestyleManager.Dispose();
            }
        }
        #endregion


        #region IServiceLocator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Get(string id, IDictionary<string, object> args)
        {
            return GetByNamedArgs(id, args);
        }

        private object GetByNamedArgs(string id, IDictionary<string, object> args)
        {
            if (!HasRegister(id))
                return null;

            var pair = IdStores[id];
            var rs = pair.LifestyleManager.Get(NewCreateContext(args, pair.Component));
            return rs;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Get(Type contract, IDictionary<string, object> args)
        {
            return GetByNamedArgs(contract, args);
        }

        private object GetByNamedArgs(Type contract, IDictionary<string, object> args)
        {
            if (!HasRegister(contract))
                return null;

            ComponentPair pair = null;
            if (contract.IsCloseGenericType() && HasRegister(contract.GetGenericTypeDefinition()))
                pair = TypeStores[contract.GetGenericTypeDefinition()][0];
            else
                pair = TypeStores[contract][0];

            return pair.LifestyleManager.Get(NewCreateContext(args, pair.Component, GetGenericParameters(contract)));
        }

        private static Type[] GetGenericParameters(Type contract)
        {
            if (contract.IsGenericType)
                return contract.GetGenericArguments();
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Get(string id, params object[] args)
        {
            return GetByOptionArgs(id, args);
        }

        private object GetByOptionArgs(string id, object[] args)
        {
            if (!HasRegister(id))
                return null;

            var pair = IdStores[id];
            var rs = pair.LifestyleManager.Get(NewCreateContext(pair.Component, args));
           
            return rs;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Get(Type contract, params object[] args)
        {
            return GetByOptionArgs(contract, args);
        }

        private object GetByOptionArgs(Type contract, object[] args)
        {
            if (!HasRegister(contract))
                return null;

            ComponentPair pair = null;
            if (contract.IsCloseGenericType() && HasRegister(contract.GetGenericTypeDefinition()))
                pair = TypeStores[contract.GetGenericTypeDefinition()][0];
            else
                pair = TypeStores[contract][0];
            return pair.LifestyleManager.Get(NewCreateContext(pair.Component, args, GetGenericParameters(contract)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public object Get(string id)
        {
            return GetByOptionArgs(id, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        /// <returns></returns>
        public object Get(Type contract)
        {
            return GetByOptionArgs(contract, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public Lazy<T> LazyGet<T>(string id)
        {
            Func<T> func = () => string.IsNullOrEmpty(id) ? (T)Get(typeof(T)) : (T)Get(id);
            return new Lazy<T>(func);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TMetadata"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public Lazy<T, TMetadata> LazyMetadataGet<T, TMetadata>(string id)
        {
            var contractType = typeof(T);
            Func<T> func = null;

            if (!string.IsNullOrEmpty(id) && HasRegister(id))
                func = () => (T)Get(id);
            else if (HasRegister(contractType))
                func = () => (T)Get(contractType);
            else
                return null;

            var info = string.IsNullOrEmpty(id) ? TypeStores[typeof(T)][0].Component : IdStores[id].Component;
            return new Lazy<T, TMetadata>(func, info.GetMetadataView<TMetadata>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contract"></param>
        /// <returns></returns>
        public IEnumerable<object> GetAll(Type contract)
        {
            if (!HasRegister(contract))
                yield break;

            var ctx = new PreCreationContext(this, GetGenericParameters(contract));

            int count = TypeStores[contract].Count;

            for (var i = 0; i < count; i++)
                yield return TypeStores[contract][i].LifestyleManager.Get(ctx.Init(TypeStores[contract][i].Component));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IEnumerable<T> GetAll<T>()
        {
            var contract = typeof(T);
            if (!HasRegister(contract))
                yield break;

            var ctx = new PreCreationContext(this, GetGenericParameters(contract));

            foreach (var item in TypeStores[contract])
                yield return Get<T>(ctx, item);
           
        }

        private static T Get<T>(PreCreationContext ctx, ComponentPair item)
        {
            return (T)item.LifestyleManager.Get(ctx.Init(item.Component));
        }

        static Lazy<T> LazyGet<T>(PreCreationContext ctx, ComponentPair item)
        {
            return new Lazy<T>(() => Get<T>(ctx, item));
        }

        static Lazy<T, TMetadata> LazyGet<T, TMetadata>(PreCreationContext ctx, ComponentPair item)
        {
            return new Lazy<T, TMetadata>(() => Get<T>(ctx, item), item.Component.GetMetadataView<TMetadata>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IEnumerable<Lazy<T>> LazyGetAll<T>()
        {
            var contract = typeof(T);
            if (!HasRegister(contract))
                yield break;

            var ctx = new PreCreationContext(this, GetGenericParameters(contract));

            foreach (var item in TypeStores[contract])
                yield return LazyGet<T>(ctx, item);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TMetadata"></typeparam>
        /// <returns></returns>
        public IEnumerable<Lazy<T, TMetadata>> LazyMetadataGetAll<T, TMetadata>()
        {
            var contract = typeof(T);
            if (!HasRegister(contract))
                yield break;

            var ctx = new PreCreationContext(this, GetGenericParameters(contract));

            foreach (var item in TypeStores[contract])
                yield return LazyGet<T,TMetadata>(ctx, item);
        }

        #endregion

        #region IServiceProvider Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public object GetService(Type serviceType)
        {
            return Get(serviceType);
        }

        #endregion


        #region IDisposable Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            //TODO:
            var items = (from item in TypeStores.Values
                         from i in item
                         select i.LifestyleManager).ToArray();

            if (items != null && items.Length > 0)
            {
                for (int i = 0; i < items.Length; i++)
                {
                    if (items[i] != null)
                        items[i].Dispose();
                }
            }

            ListenerManager.Dispose();
        }

        #endregion

    }
}
